import axios, {
  AxiosError,
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  CreateAxiosDefaults,
  InternalAxiosRequestConfig
} from "axios";
import { showToast } from "vant";

const RequestErrorCode = "REQUEST_ERROR";

// 返回数据类型map
type ResponseMap = {
  blob: Blob;
  text: string;
  arrayBuffer: ArrayBuffer;
  stream: ReadableStream<Uint8Array>;
  document: Document;
};

// 返回体数据类型
type ResponseType = keyof ResponseMap | "json";

type MappedType<
  R extends ResponseType,
  JsonType = any
> = R extends keyof ResponseMap ? ResponseMap[R] : JsonType;

// json返回体
type ResponseData<T = any> = {
  code: string;
  message: string;
  data: T;
};

type RequestOption<T = ResponseData> = {
  onRequest: (
    config: InternalAxiosRequestConfig
  ) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
  isSuccess: (response: AxiosResponse<T>) => boolean;
  onFail: (
    response: AxiosResponse<T>,
    instance: AxiosInstance
  ) => Promise<AxiosResponse<T> | null> | Promise<void>;
  onError: (error: AxiosError<T>) => any | Promise<any>;
  transformResponse: (response: AxiosResponse<T>) => T;
};

function createDefaultOptions(options?: Partial<RequestOption>) {
  const opts: RequestOption = {
    onRequest: async config => config,
    isSuccess: () => true,
    onFail: async () => {},
    onError: async () => {},
    transformResponse: response => response.data
  };
  Object.assign(opts, options || {});
  return opts;
}

function createInstance(
  axiosConfig: CreateAxiosDefaults,
  options?: Partial<RequestOption>
): { instance: AxiosInstance; opts: RequestOption } {
  const opts = createDefaultOptions(options);

  const instance = axios.create(axiosConfig);

  instance.interceptors.request.use(
    async config => {
      const handledConfig = (await opts.onRequest?.(config)) || config;
      return handledConfig;
    },
    error => {
      return Promise.reject(error);
    }
  );

  instance.interceptors.response.use(
    async response => {
      const responseType = response.config?.responseType || "json";

      if (responseType !== "json" || opts.isSuccess(response)) {
        return Promise.resolve(response);
      }

      const fail = await opts.onFail(response, instance);
      if (fail) {
        return fail;
      }

      const Error = new AxiosError(
        "the request error",
        RequestErrorCode,
        response.config,
        response.request,
        response
      );

      await opts.onError(Error);

      return Promise.reject(Error);
    },
    async (error: AxiosError<ResponseData>) => {
      await opts.onError(error);

      return Promise.reject(error);
    }
  );

  return { instance, opts };
}

function createRequest(
  axiosConfig: CreateAxiosDefaults,
  options?: Partial<RequestOption>
) {
  const { instance, opts } = createInstance(axiosConfig, options);

  const request = async <T = any, R extends ResponseType = "json">(
    config: AxiosRequestConfig
  ) => {
    try {
      const response: AxiosResponse<ResponseData<T>> = await instance(config);
      const responseType = response.config?.responseType || "json";

      if (responseType === "json") {
        const data = opts.transformResponse(response) as unknown as T;

        return { data, error: null, response };
      }

      return {
        data: response.data as unknown as MappedType<R, T>,
        error: null,
        response
      };
    } catch (error) {
      return {
        data: null,
        error: error as AxiosError<ResponseData<T>>,
        response: (error as AxiosError<ResponseData<T>>).response
      };
    }
  };

  return request;
}

const request = createRequest(
  { baseURL: "http://localhost:3000" },
  {
    onRequest: config => {
      const { headers } = config;
      headers["token"] = "test-123";
      return config;
    },
    isSuccess: response => {
      return response.data.code === "000000";
    },
    onFail: async response => {
      const { code } = response.data;
      const expiredCode =
        import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODE?.split(",") || [];

      if (expiredCode.includes(code)) {
        // 退出登录
        return null;
      }

      return null;
    },
    onError: error => {
      let { message } = error;
      let errorCode = "";
      if (error.code === RequestErrorCode) {
        message = error.response?.data?.message || message;
        errorCode = String(error.response?.data?.code || "");
      }

      // 根据code做一些业务的特殊判断
      const expiredCode =
        import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODE?.split(",") || [];
      if (expiredCode.includes(errorCode)) {
        return;
      }

      showToast(message);
    },
    transformResponse: response => {
      return response.data.data;
    }
  }
);

export default request;
